
package com.ys.chatserver.im.impl;

import cn.hutool.core.date.DateUtil;
import com.eva.framework.dbpool.DBShell;
import com.eva.framework.utils.LoggerFactory;
import com.google.gson.Gson;
import com.ys.chatserver.common.dto.cnst.UserProtocalsType;
import com.ys.chatserver.im.dto.*;
import com.ys.chatserver.im.util.MessageHelper;
import net.ys.mobileimsdk.server.protocal.Protocal;

import java.util.Date;

/**
 * 离线消息处理逻辑管理器。
 * <p>
 * 所有有关离线的处理逻辑都在本类中集中完成，方便统一管理。
 *
 * <p>
 * <b>离线消息处理逻辑在什么地方实现合适？</b><br><br>
 * 详见“RainbowChat_pro_v4关键设计思路备忘.txt”。
 *
 * @author Tony
 * @since 4.5
 */
public class OfflineMessageManager {
    private static final DBShell db = new DBShell();

//	/**
//	 * 处理离线群聊指令/通知（用于群聊的指令和通知）.
//	 * 
//	 * @param p 原始Protocal协议包对象
//     * @param onlyDoAppPush true表示不做其它任何离线处理逻辑只做App的Push通知，否则正常走离线处理完整逻辑（包括App的
//     * Push通知），正常情况本参数设为false即可。为true时，用于桥接通信成功，但手机端确实不在本App Server上，那么此时
//     * 做一个only push（不需要离线存储逻辑，否则web那端再搞离线存储那消息就重复了），是合理的，这就是App这端的特殊情况！
//     * 
//	 * @return
//	 * @see Protocal
//	 */
//	public static boolean processOfflineGroupSysCMD(Protocal p, boolean onlyDoAppPush)
//	{
//		// 消息的接收者
//		String userId = p.getTo();
//		// 消息的发出者
//		String from_user_id = "0";
//		String fingerPrint = p.getFp();
//		// 本字段只有在聊天模式类型chatType为“2 群聊”时有意义且不应为空，表示本条群聊消息发于在哪个群里
//		String group_id = null;
//		
//		String msg = null;
//		
//		// 按指含类型解析协议包并处理离线逻辑
//		switch(p.getTypeu())
//		{
//			case UserProtocalsType.MT46_OF_GROUP$SYSCMD_MYSELF$BE$INVITE_FROM$SERVER:	
//			{
//				CMDBody4MyselfBeInvitedGroupResponse cmdBody = new Gson().fromJson(
//						p.getDataContent(), CMDBody4MyselfBeInvitedGroupResponse.class);
//				
//				msg = "\""+cmdBody.getInitveBeNickName()+"\"邀请你加入了群聊";
//				group_id = cmdBody.getG_id();
//				
//				break;
//			}
//			case UserProtocalsType.MT47_OF_GROUP$SYSCMD_COMMON$INFO_FROM$SERVER:
//			case UserProtocalsType.MT48_OF_GROUP$SYSCMD_DISMISSED_FROM$SERVER:
//			case UserProtocalsType.MT49_OF_GROUP$SYSCMD_YOU$BE$KICKOUT_FROM$SERVER:
//			case UserProtocalsType.MT50_OF_GROUP$SYSCMD_SOMEONEB$REMOVED_FROM$SERVER:
//			{
//				MsgBody4Group body = new Gson().fromJson(p.getDataContent(), MsgBody4Group.class);
//				msg = body.getM();
//				group_id = body.getT();
//				break;
//			}
//			case UserProtocalsType.MT51_OF_GROUP$SYSCMD_GROUP$NAME$CHANGED_FROM$SERVER:	
//			{
//				CMDBody4GroupNameChangedNotification cmdBody = 
//						new Gson().fromJson(p.getDataContent(), CMDBody4GroupNameChangedNotification.class);
//				msg = cmdBody.getNotificationContent();
//				group_id = cmdBody.getGid();
//				break;
//			}
//		}
//		
//		if(onlyDoAppPush)
//		{
//			try 
//			{
//				//## 尝试向ios设备进行APNs离线消息推送（推送是基于netty+HTTP/2实现，因而是异步的不会阻塞余下代码执行）
//				APNSPushHelper.tryPushChattingMessage(chatType, msgType, content, fromUid, toUid, toGid);
////				APNSPushHelper.tryPushGroupCMD(userId, group_id, msg);
//				return true;
//			} 
//			catch (Exception e)
//			{
//				LoggerFactory.getLog().warn(e.getMessage(), e);
//				return false;
//			}
//		}
//		else
//		{
//			LoggerFactory.getLog().debug("[RBChat]【离线存储】【群聊CMD】[gid="+group_id
//					+"]正在处理："+from_user_id+"发给"+userId+"的离线系统通知：str="+msg
//					+",fingerPring="+fingerPrint+"......");
//			try
//			{
//				if(userId != null && from_user_id != null && msg != null && group_id != null)
//				{
//					//## 尝试向ios设备进行APNs离线消息推送（推送是基于netty+HTTP/2实现，因而是异步的不会阻塞余下代码执行）
//					APNSPushHelper.tryPushChattingMessage(chatType, msgType, content, fromUid, toUid, toGid);
////					APNSPushHelper.tryPushGroupCMD(userId, group_id, msg);
//
//					//## 将该条群聊系统通知插入到数据库中（以备接收者上线后拿到）
//					insertOffline(from_user_id
//							, userId
//							, ""+MsgBodyRoot.TYPE_SYSTEAM$INFO
//							, msg
//							, fingerPrint
//							, ""+System.currentTimeMillis()
//							, MsgBodyRoot.CHAT_TYPE_GROUP$CHAT
//							, group_id);
//
//					return true;
//				}
//				else
//				{
//					LoggerFactory.getLog().error("[RBChat]【群聊CMD】[gid="+group_id
//							+"]离线群通知数据有误，该条离线系统通知无法存储到DB中：userId="
//							+userId+", from_user_id="+from_user_id+", msg="+msg+")");
//				}
//			}
//			catch (Exception e)
//			{
//				LoggerFactory.getLog().warn("[RBChat]【离线存储】【群聊CMD】[gid="+group_id
//						+"]处理离线存储系统通知出错了 -> "+from_user_id+"发给"+userId
//						+"的离线系统通知：str="+msg, e);
//			}
//			
//			return false;
//		}
//	}

    /**
     * 处理离线聊天消息（专用于普通聊天消息(含一对一、群聊））
     *
     * @param p             原始Protocal协议包对象
     * @param onlyDoAppPush true表示不做其它任何离线处理逻辑只做App的Push通知，否则正常走离线处理完整逻辑（包括App的
     *                      Push通知），正常情况本参数设为false即可。为true时，用于桥接通信成功，但手机端确实不在本App Server上，那么此时
     *                      做一个only push（不需要离线存储逻辑，否则web那端再搞离线存储那消息就重复了），是合理的，这就是App这端的特殊情况！
     */
    public static boolean processOfflineMessage(Protocal p, boolean onlyDoAppPush, String time) {
        // 接收者uid
        String userId = null;
        // 发送者uid
        String from_user_id = null;
        // 消息或指令内容（一般而言，这会是个JSON字符串，因为是个复合对象）
        String dataContent = p.getDataContent();
        // 消息或指令指纹码（即唯一ID）
        String fingerPrint = p.getFp();

        // 用于离线存储时的聊天模式类型（见 MsgBodyRoot中的常量定义 ）
        int chatType = -1;
        // 用于离线存储时的消息内容类型（见 MsgBodyRoot中的常量定义 ）
        int msgType = -1;

        // 本字段只有在聊天模式类型chatType为“2 群聊”时有意义且不应为空，表示本条群聊消息发于在哪个群里
        String group_id = null;
        // 本字段只有在聊天模式类型chatType为“2 群聊”时有意义，表示群聊消息扩散写前原始消息的指纹码（目前主要用于消息“撤回”功能时）
        String parent_fp = null;

        String msg = null;

        switch (p.getTypeu()) {
            // 一对一好友聊天消息用于离线处理的协议解析逻辑（C2C方向）
            case UserProtocalsType.MT03_OF_CHATTING_MESSAGE: {
                MsgBodyRoot msgBody = MsgBodyRoot.parseFromSender(dataContent);

                userId = p.getTo();
                from_user_id = p.getFrom();
                chatType = msgBody.getCy();//MsgBodyRoot.CHAT_TYPE_FREIDN$CHAT;

                group_id = null;
                parent_fp = null;
                msg = msgBody.getM();
                msgType = msgBody.getTy();

                break;
            }
            // 一对一陌生人聊天消息用于离线处理的协议解析逻辑（S2C方向）
            case UserProtocalsType.MT43_OF_TEMP$CHAT$MSG_SERVER$TO$B: {
                // 解析临时聊天消息体内容(dataContent直接传的是Protocal的content字段哦，也就是MsgBody4Guest的JSON串！)
                MsgBody4Guest msgBody = MessageHelper.pareseTempChatMsg_A$TO$SERVER_Message(dataContent);

                userId = msgBody.getT();
                from_user_id = msgBody.getF();
                chatType = msgBody.getCy();

                group_id = null;
                parent_fp = null;
                msg = msgBody.getM();
                msgType = msgBody.getTy();

                break;
            }
            // 一对对群聊天消息用于离线处理的协议解析逻辑（S2C方向）
            case UserProtocalsType.MT45_OF_GROUP$CHAT$MSG_SERVER$TO$B:
            case UserProtocalsType.MT71_OF_PTT_VOICE_NOTIVICATION: {
                // 解析群组聊天消息体内容(dataContent直接传的是Protocal的content字段哦，也就是MsgBody4Guest的JSON串！)
                MsgBody4Group msgBody = MessageHelper.pareseGroupChatMsg_A$TO$SERVER_Message(dataContent);

                userId = p.getTo();// 本条扩散写消息的接收人
                from_user_id = msgBody.getF();
                chatType = msgBody.getCy();

                group_id = msgBody.getT(); // 群聊时，协议体msgBody中的t字段存放的就是群id（这是在im协议里约定好的，详见IM协议手册）
                parent_fp = msgBody.getParentFp();
                msg = msgBody.getM();
                msgType = msgBody.getTy();

                break;
            }

            /******************************* 以下指令类型属于群聊系统通知 START *************************************/
            case UserProtocalsType.MT46_OF_GROUP$SYSCMD_MYSELF$BE$INVITE_FROM$SERVER: {
                CMDBody4MyselfBeInvitedGroupResponse cmdBody = new Gson().fromJson(
                        p.getDataContent(), CMDBody4MyselfBeInvitedGroupResponse.class);

                userId = p.getTo();// 本条扩散写消息的接收人
                from_user_id = "0";// 消息的发起人（这是“服务端”发出的，所以是“0”）
                chatType = MsgBodyRoot.CHAT_TYPE_GROUP$CHAT;

                group_id = cmdBody.getG_id();
                parent_fp = null;
                msg = "\"" + cmdBody.getInitveBeNickName() + "\"邀请你加入了群聊";
                msgType = MsgBodyRoot.TYPE_SYSTEAM$INFO;

                break;
            }
            case UserProtocalsType.MT47_OF_GROUP$SYSCMD_COMMON$INFO_FROM$SERVER:
            case UserProtocalsType.MT48_OF_GROUP$SYSCMD_DISMISSED_FROM$SERVER:
            case UserProtocalsType.MT49_OF_GROUP$SYSCMD_YOU$BE$KICKOUT_FROM$SERVER:
            case UserProtocalsType.MT50_OF_GROUP$SYSCMD_SOMEONEB$REMOVED_FROM$SERVER: {
                MsgBody4Group body = new Gson().fromJson(p.getDataContent(), MsgBody4Group.class);

                userId = p.getTo();// 本条扩散写消息的接收人
                from_user_id = "0";// 消息的发起人（这是“服务端”发出的，所以是“0”）
                chatType = MsgBodyRoot.CHAT_TYPE_GROUP$CHAT;

                group_id = body.getT();
                parent_fp = null;
                msg = body.getM();
                msgType = MsgBodyRoot.TYPE_SYSTEAM$INFO;

                break;
            }
            case UserProtocalsType.MT51_OF_GROUP$SYSCMD_GROUP$NAME$CHANGED_FROM$SERVER: {
                CMDBody4GroupNameChangedNotification cmdBody = new Gson().fromJson(p.getDataContent(), CMDBody4GroupNameChangedNotification.class);

                userId = p.getTo();// 本条扩散写消息的接收人
                from_user_id = "0";// 消息的发起人（这是“服务端”发出的，所以是“0”）
                chatType = MsgBodyRoot.CHAT_TYPE_GROUP$CHAT;
                //shi zheme huishier
                group_id = cmdBody.getGid();
                parent_fp = null;
                msg = cmdBody.getNotificationContent();
                msgType = MsgBodyRoot.TYPE_SYSTEAM$INFO;

                break;
            }
            /******************************* 以下指令类型属于群聊系统通知 END *************************************/
        }

        if (onlyDoAppPush) {
            try {
//				MsgBodyRoot tm = MsgBodyRoot.parseFromSender(dataContent);

                //## 尝试向ios设备进行APNs离线消息推送（推送是基于netty+HTTP/2实现，因而是异步的不会阻塞余下代码执行）
                //APNSPushHelper.tryPushChattingMessage(chatType, msgType, msg, from_user_id, userId, group_id);

                return true;
            } catch (Exception e) {
                LoggerFactory.getLog().warn(e.getMessage(), e);
                return false;
            }
        } else {
            LoggerFactory.getLog().debug("[RBChat]【离线存储】[chatType=" + chatType + ",msgType=" + msgType + ",gid=" + group_id
                    + "]正在处理：" + from_user_id + "发给" + userId + "的离线消息：str=" + dataContent + ",fingerPring=" + fingerPrint + ",parent_fp=" + parent_fp + "......");
            try {
//				MsgBodyRoot tm = MsgBodyRoot.parseFromSender(dataContent);

//				if(tm != null && tm.getF() != null && tm.getT() != null)
                if (msg != null && from_user_id != null && userId != null) {
                    if (fingerPrint != null && !db.queryData("select 1 from chat_offline_history where " +
                            "	history_content2 is not null and history_content2='" + fingerPrint + "'").isEmpty()) {
                        LoggerFactory.getLog().debug("[RBChat]【离线存储】[chatType=" + chatType + ",msgType=" + msgType + ",gid=" + group_id
                                + "]" + from_user_id + "发给" + userId + "的离线消息：str=" + dataContent
                                + ",fingerPring=" + fingerPrint + ",parent_fp=" + parent_fp + " 已经被放入DB中存储了，本条可能是客户" + "端QoS机制重复发过来的，将被忽略(无需存储).");
                    } else {
                        //## 尝试向ios设备进行APNs离线消息推送（推送是基于netty+HTTP/2实现，因而是异步的不会阻塞余下代码执行）
                        //APNSPushHelper.tryPushChattingMessage(chatType, msgType, msg, from_user_id, userId, group_id);

                        // 将该条聊天消息插入到数据库中（以备下次接收方B上线后拿到）
                        insertOffline(from_user_id
                                , userId
                                , String.valueOf(msgType)
                                , msg
                                , fingerPrint
                                , p.getSm() + ""
                                , chatType
                                , group_id
                                , parent_fp, time);
                    }
                } else {
                    LoggerFactory.getLog().error("[RBChat]【离线存储】[chatType=" + chatType + ",msgType=" + msgType + ",gid=" + group_id
                            + "]离线消息数据有误，该条离线消息无法存储到DB中：("
                            + from_user_id + "发给" + userId + "的离线消息：str=" + dataContent + ",fingerPring=" + fingerPrint + ",parent_fp=" + parent_fp + ")");
                }
                return true;
            } catch (Exception e) {
                LoggerFactory.getLog().debug("[RBChat]【离线存储】[chatType=" + chatType + ",msgType=" + msgType + ",gid=" + group_id
                        + "]处理离线存储消息时出错了 -> " + from_user_id + "发给" + userId + "的离线消息：str=" + dataContent + ",fingerPring=" + fingerPrint + ",parent_fp=" + parent_fp, e);
            }

            return false;
        }
    }

    /**
     * 插入一条离线消息（包括文字、图片消息、语音留言消息、各种指令等）到数据库.
     *
     * @param toUid           消息的接收者
     * @param fromUid         消息的发出者
     * @param msgType         聊天消息类型，详见{@link MsgBodyRoot}
     * @param message         聊天消息内容
     * @param messageContent2 本字段用于存放fingerPrint指纹码
     * @param chatType        聊天模式类型，详见 {@link MsgBodyRoot}
     * @param group_id        本字段只有在聊天模式类型chatType为“2 群聊”时有意义且不应为空，表示本条群聊消息发于在哪个群里
     * @param parent_fp       本字段只有在聊天模式类型chatType为“2 群聊”时有意义，表示群聊消息扩散写前原始消息的指纹码（目前主要用于消息“撤回”功能时）
     * @throws Exception e
     */
    private static void insertOffline(String fromUid, String toUid
            , String msgType, String message, String messageContent2, String history_time2
            , int chatType, String group_id, String parent_fp, String time) throws Exception {
        String format = DateUtil.format(new Date(Long.parseLong(time)), "yyyy-MM-dd HH:mm:ss");
        // 将该条临时聊天消息插入到数据库中（以备下次接收方B上线后拿到）
        db.update(
                "INSERT INTO chat_offline_history(user_uid,friend_user_uid, chat_type,history_type" +
                        ",history_content,history_content2,history_time2,history_time,group_id,parent_fp)" +
                        " VALUES(?,?,?,?,?,?,?,?,?,?)"
                , new Object[]{fromUid
                        , toUid
                        , chatType
                        , msgType
                        , message
                        , messageContent2
                        , history_time2
                        , format
                        , group_id // 本字段只有在聊天模式类型chatType为“2 群聊”时有意义且不为空。表示本条群聊消息发于在哪个群里
                        , parent_fp
                }
                , false);
    }
}
